/*
 * Niiuzu
 * Copyright (C) 2011  Thierry Nowak
 * 
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.niiuzu.client.screen.application;

import java.util.PriorityQueue;
import java.util.Queue;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.inject.Inject;
import com.mvp4g.client.annotation.Presenter;
import com.mvp4g.client.presenter.LazyPresenter;
import com.niiuzu.client.eventbus.ApplicationEventBus;
import com.niiuzu.client.i18n.CommonMessages;
import com.niiuzu.client.util.ClientContextFactory;
import com.niiuzu.client.util.LoadingTicket;
import com.niiuzu.client.util.SessionClient;
import com.niiuzu.client.util.cookie.CookieManager;
import com.niiuzu.client.util.cookie.CookieName;
import com.niiuzu.client.util.images.BundleFactory;
import com.niiuzu.client.util.images.main.MainImageBundle;
import com.niiuzu.shared.model.User;
import com.niiuzu.shared.model.UserLogInResult;
import com.niiuzu.shared.rpc.UserServiceAsync;

/**
 * Presenter principal de l'application en charge du routage des événements
 * principaux de l'application (notamment le passage d'un module à un second).
 */
@Presenter(view = ApplicationViewImpl.class)
public class ApplicationPresenter extends
		LazyPresenter<ApplicationView, ApplicationEventBus> {

	static final String LOCALE_FR = "fr";
	static final String LOCALE_EN = "en";

	@Inject
	private UserServiceAsync userService;

	private CommonMessages commonMessages = GWT.create(CommonMessages.class);

	private MainImageBundle mainImageBundle = BundleFactory
			.getMainImageBundle();

	private UncaughtExceptionHandler uncaughtExceptionHandler;

	private ClickHandler logOutClickHandler;

	private Queue<LoadingTicket> loadingTicketsQueue;

	@Override
	public void createPresenter() {
		super.createPresenter();

		loadingTicketsQueue = new PriorityQueue<LoadingTicket>();
	}

	@Override
	public void bindView() {
		super.bindView();

		uncaughtExceptionHandler = new UncaughtExceptionHandler() {
			@Override
			public void onUncaughtException(Throwable e) {
				eventBus.technicalErrorOccurred(e);
			}
		};
		GWT.setUncaughtExceptionHandler(uncaughtExceptionHandler);

		logOutClickHandler = new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				logOutUser();
			}
		};
		view.getLogOutHasClickHandlers().addClickHandler(logOutClickHandler);

		ClickHandler flagHandler;

		flagHandler = createFlagButtonHandler(LOCALE_FR);
		view.addFlagButton(mainImageBundle.flagFr(), commonMessages
				.flagMessageFr(), flagHandler);

		flagHandler = createFlagButtonHandler(LOCALE_EN);
		view.addFlagButton(mainImageBundle.flagEn(), commonMessages
				.flagMessageEn(), flagHandler);
	}

	/**
	 * Déconnexion de l'utilisateur.
	 */
	protected void logOutUser() {

		CookieManager cookieManager = getCookieManager();
		cookieManager.removeCookie(CookieName.REMEMBER_USER_REFERENCE);

		SessionClient sessionClient = SessionClient.getInstance();
		sessionClient.setUser(null);
		sessionClient.clearChannels();

		view.hideLogStatusPanel();

		eventBus.homeLoadStarting();
	}

	/**
	 * Crée un {@link ClickHandler} qui sera déclenché lors d'un clic sur un
	 * bouton de changement de la langue.
	 * 
	 * @param locale
	 *            valeur de la locale.
	 */
	private ClickHandler createFlagButtonHandler(String locale) {

		String currentHref = getCurrentHref();
		final String link = constructUrlWithNewLocale(currentHref, locale);
		ClickHandler handler = new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				Window.Location.replace(link);
			}
		};

		return handler;
	}

	/**
	 * Récupère l'URL affichée actuellement dans le navigateur.
	 */
	private String getCurrentHref() {
		if (mockCurrentHref != null) {
			return mockCurrentHref;
		}
		return Window.Location.getHref();
	}

	/**
	 * Construit l'URL qui permettra de changer la <tt>locale</tt>.
	 * 
	 * @param currentHref
	 *            URL affichée dans le navigateur.
	 * @param locale
	 *            valeur de la locale à afficher.
	 */
	String constructUrlWithNewLocale(String currentHref, String locale) {
		StringBuffer urlBuffer = new StringBuffer();

		// Sans séparateur de paramètres
		if (!currentHref.contains("?")) {
			urlBuffer.append(currentHref);
			urlBuffer.append("?locale=");
			urlBuffer.append(locale);
		}
		// Avec séparateur de paramètres
		else {
			String[] split = currentHref.split("\\?");
			urlBuffer.append(split[0]);
			urlBuffer.append("?");

			// Si des paramètres existent
			if (split.length > 1) {
				String queryString = split[1];
				for (String parameter : queryString.split("&")) {
					if (!parameter.startsWith("locale")) {
						urlBuffer.append(parameter);
						urlBuffer.append("&");
					}
				}
			}

			urlBuffer.append("locale=");
			urlBuffer.append(locale);
		}

		return urlBuffer.toString();
	}

	/**
	 * Chargement du module principal suite à la connexion d'un utilisateur à
	 * l'application.
	 * 
	 * @see ApplicationEventBus#userEnteredInApplication()
	 */
	public void onUserEnteredInApplication() {

		CookieManager cookieManager = getCookieManager();
		String userReference = cookieManager
				.getCookieValue(CookieName.REMEMBER_USER_REFERENCE);

		view.showLoadingApplication();

		if (userReference == null) {
			eventBus.homeLoadStarting();
		} else {
			userService.logUserInAtStartUp(userReference, ClientContextFactory
					.get(), new AsyncCallback<UserLogInResult>() {
				@Override
				public void onSuccess(UserLogInResult result) {
					recoverUserAtStartUp(result);
				}

				@Override
				public void onFailure(Throwable caught) {
					eventBus.homeLoadStarting();
				}
			});
		}
	}

	/**
	 * Récupération de l'utilisateur au démarrage de l'application.
	 */
	void recoverUserAtStartUp(UserLogInResult result) {
		if (result == null) {
			eventBus.homeLoadStarting();
		} else {
			User user = result.getUser();
			SessionClient.getInstance().setUser(user);

			view.showLogStatusPanel(user.getMail());
			eventBus.feedMainViewLoadStarting(null);
		}
	}

	/**
	 * Réception de la vue de la page d'accueil.
	 * 
	 * @see ApplicationEventBus#homeLoaded(IsWidget)
	 */
	public void onHomeLoaded(IsWidget widget) {

		view.removeLoadingApplication();

		view.setBodyContent(widget);
		view.showBodyPanel();
	}

	/**
	 * Réception de la vue du gestionnaire des données de flux RSS.
	 * 
	 * @see ApplicationEventBus#feedMainViewLoaded(IsWidget, LoadingTicket)
	 */
	public void onFeedMainViewLoaded(IsWidget widget, LoadingTicket ticket) {

		view.removeLoadingApplication();

		view.setBodyContent(widget);
		view.showBodyPanel();

		eventBus.loadingFinished(ticket);
	}

	/**
	 * Routage vers le gestionnaire des flux suite à l'authentification de
	 * l'utilisateur.
	 * 
	 * @see ApplicationEventBus#userLoggedIn(LoadingTicket)
	 */
	public void onUserLoggedIn(LoadingTicket ticket) {

		String emailAddress = SessionClient.getInstance().getUser().getMail();
		view.showLogStatusPanel(emailAddress);

		LoadingTicket feedLoadingTicket = LoadingTicket.Factory.createTicket(
				getClass(), commonMessages.feedLoadingMessage());
		eventBus.loadingStarted(feedLoadingTicket);
		eventBus.feedMainViewLoadStarting(feedLoadingTicket);

		eventBus.loadingFinished(ticket);
	}

	/**
	 * Traitement appliqué lorsqu'une erreur technique s'est déclenchée dans
	 * l'application.
	 * 
	 * @see ApplicationEventBus#technicalErrorOccurred(Throwable)
	 */
	public void onTechnicalErrorOccurred(Throwable caught) {
		GWT.log("Erreur technique", caught);

		view.showPopupError(caught);
	}

	/**
	 * Réception d'un message d'erreur qui sera affiché à l'écran.
	 * 
	 * @see ApplicationEventBus#businessErrorOccurred(String)
	 */
	public void onBusinessErrorOccurred(String errorMessage) {
		view.showPopupErrorMessage(errorMessage);
	}

	/**
	 * Gestionnaire des erreurs techniques générées côté client (GWT).
	 */
	UncaughtExceptionHandler getUncaughtExceptionHandler() {
		return uncaughtExceptionHandler;
	}

	/**
	 * Affichage de la popup de chargement suite à la réception de l'événement.
	 * 
	 * @see ApplicationEventBus#loadingStarted(LoadingTicket)
	 */
	public void onLoadingStarted(LoadingTicket ticket) {
		if (ticket != null && !loadingTicketsQueue.contains(ticket)) {

			int queueSize = loadingTicketsQueue.size();
			loadingTicketsQueue.add(ticket);

			if (queueSize == 0) {
				String message = "";
				if (ticket.getMessage() == null) {
					message = commonMessages.defaultLoadingMessage();
				} else {
					message = ticket.getMessage();
				}

				view.showLoadingMessage(message);
			}
		}
	}

	/**
	 * Affichage de la popup de chargement suite à la réception de l'événement.
	 * 
	 * @see ApplicationEventBus#loadingFinished(LoadingTicket)
	 */
	public void onLoadingFinished(LoadingTicket ticket) {
		if (ticket != null && loadingTicketsQueue.contains(ticket)) {

			loadingTicketsQueue.remove(ticket);

			if (loadingTicketsQueue.size() == 0) {
				view.hideLoadingMessage();
			} else {
				LoadingTicket nextTicket = loadingTicketsQueue.peek();

				String message = "";
				if (nextTicket.getMessage() == null) {
					message = commonMessages.defaultLoadingMessage();
				} else {
					message = nextTicket.getMessage();
				}

				view.showLoadingMessage(message);
			}
		}
	}

	/**
	 * Récupération d'une instance de {@link CookieManager}.
	 */
	private CookieManager getCookieManager() {
		if (mockedCookieManager != null) {
			return mockedCookieManager;
		}
		return new CookieManager();
	}

	/**
	 * Comportement d'un clic sur le lien de déconnexion.
	 */
	ClickHandler getLogOutClickHandler() {
		return logOutClickHandler;
	}

	/* ********************************************************************** */
	/* ********************************************************************** */
	/* ********************************************************************** */

	private String mockCurrentHref = null;
	private CookieManager mockedCookieManager = null;

	/**
	 * JUnit - Définit un mock pour {@link CommonMessages}.
	 */
	public void setMockCommonMessages(CommonMessages commonMessages) {
		this.commonMessages = commonMessages;
	}

	/**
	 * JUnit - Définit un mock pour {@link MainImageBundle}.
	 */
	void setMockMainImageBundle(MainImageBundle mainImageBundle) {
		this.mainImageBundle = mainImageBundle;
	}

	/**
	 * JUnit - Définit un mock pour la valeur de l'URL en cours.
	 */
	void setMockCurrentHref(String currentHref) {
		this.mockCurrentHref = currentHref;
	}

	/**
	 * JUnit - Définit un mock pour {@link CookieManager}.
	 */
	void setMockCookieManager(CookieManager cookieManager) {
		this.mockedCookieManager = cookieManager;
	}

	/**
	 * JUnit - Définit un mock pour {@link UserServiceAsync}.
	 */
	void setMockUserService(UserServiceAsync userService) {
		this.userService = userService;
	}
}
